package com.cordys.opensource.crom;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cordys.cpc.bsf.busobject.BusObject;
import com.cordys.cpc.bsf.busobject.BusObjectArray;
import com.cordys.cpc.bsf.busobject.BusObjectIterator;
import com.cordys.cpc.bsf.busobject.BusObjectUID;
import com.cordys.cpc.bsf.busobject.QueryObject;
import com.cordys.cpc.bsf.busobject.exception.BsfApplicationRuntimeException;
import com.cordys.cpc.bsf.busobject.exception.BsfRuntimeException;
import com.cordys.cpc.bsf.classinfo.AttributeInfo;
import com.cordys.cpc.bsf.classinfo.RelationInfo_Composite;
import com.cordys.cpc.bsf.classinfo.RelationInfo_FK;
import com.cordys.cpc.bsf.relation.MultiRelation;
import com.cordys.cpc.bsf.relation.SingleRelation;
import com.cordys.opensource.crom.sql.Query;
import com.cordys.opensource.crom.sql.Table;
import com.cordys.opensource.crom.sql.Where.Operator;

/**
 * This class is a wrapper around the metadata of a BusObject and it's composite children.
 *  It is used to compose queries, and to map the query result back into a proper BusObject.
 * <br/>Note: the BusObject will mostly be a custom BusObject, otherwise it will have composite children,
 * and the resulting queries will not be anything fancy. 
 *
 * @param <T> The type of the result of the query (e.g., crom.example.domain.Ticket).
 */
public class DomainObjectQuery<T extends BusObject>
{
	/**
	 * Returns a DomainObjectQuery for the specified class.
	 * @param <O>
	 * @param domainClass The domain class for which to create the query
	 * @param deep Whether to create a query with children or not.
	 * @return
	 */
	public static <O extends BusObject> DomainObjectQuery<O> getQuery(Class<O> domainClass, boolean deep)
	{
		BusObjectMetadata metadata = BusObjectMetadata.getMetadata(domainClass);
		if (metadata==null)
		{
			throw new BsfRuntimeException("Cannot find metadata for class "+domainClass.getName()+", check whether it has the DomainObject annotation");
		}
		return new DomainObjectQuery<O>(metadata, deep);
	}

	/**
	 * Returns a query for the child class associated with the childRelation in the domain class.  E.g., <br/><br/>
	 * <code>DomainObjectQuery&lt;User&gt; doq = DomainObjectQuery.getChildQuery(Ticket.class, "Reporter", true);</code> 
	 * @param <O>
	 * @param domainClass
	 * @param childRelation
	 * @param deep
	 * @return
	 */
	public static <O extends BusObject> DomainObjectQuery<O> getChildQuery(Class<? extends BusObject> domainClass, String childRelation, boolean deep)
	{
		BusObjectMetadata metadata = BusObjectMetadata.getMetadata(domainClass);
		if (metadata==null)
		{
			throw new BsfRuntimeException("Cannot find metadata for class "+domainClass.getName()+", check whether it has the DomainObject annotation");
		}
		BusObjectMetadata childMetadata = metadata.getRelation(childRelation);
		return new DomainObjectQuery<O>(childMetadata, deep);
	}
	
	/**
	 * Returns a query for the grandChildRelation. The domain class must have a relation with the name of childRelation, which in turn must have a relation 
	 * with the name of the grandChildRelation. E.g., <br/><br/>
	 * <code>DomainObjectQuery&lt;User&gt; doq = DomainObjectQuery.getGrandChildQuery(Ticket.class, "Project", "User", true);</code> 
	 * @param <O>
	 * @param domainClass
	 * @param childRelation
	 * @param grandChildRelation
	 * @param deep
	 * @return
	 */
	public static <O extends BusObject> DomainObjectQuery<O> getGrandChildQuery(Class<? extends BusObject> domainClass, String childRelation, String grandChildRelation, boolean deep)
	{
		BusObjectMetadata metadata = BusObjectMetadata.getMetadata(domainClass);
		if (metadata==null)
		{
			throw new BsfRuntimeException("Cannot find metadata for class "+domainClass.getName()+", check whether it has the DomainObject annotation");
		}
		
		BusObjectMetadata childMetadata = metadata.getRelation(childRelation);
		if (childMetadata==null)
		{
			throw new BsfRuntimeException("Cannot find a relationship named "+childRelation+" in class "+domainClass.getName());
		}
		
		BusObjectMetadata grandChildMetadata = childMetadata.getRelation(grandChildRelation);
		if (grandChildMetadata==null)
		{
			throw new BsfRuntimeException("Cannot find a relationship named "+grandChildRelation+" in class "+childMetadata.getDomainClass().getName());
		}
		
		return new DomainObjectQuery<O>(grandChildMetadata, deep);
	}
	
	
	private final Class<T> domainClass;
	private final Class<? extends BusObject> dbClass;
	private final Class<? extends BusObject> dbRevisionClass;
	private final BusObjectMetadata metadata;
	private final DomainObjectQuery<? extends BusObject> parentDomainQuery;
	private final String aliasName;
	private final Set<BusObjectAttribute> attributes = new HashSet<BusObjectAttribute>();
	private final Map<BusObjectMetadata, DomainObjectQuery<?>> compositeChildren = new LinkedHashMap<BusObjectMetadata, DomainObjectQuery<?>>();
	private final Query query;
	private final Table fromDBTable;
	private Table fromRevisionTable;
	private Table fromMappingTable;
	
	/**
	 * Create a new DomainObjectQuery for the domain class. If deep is true, then it will
	 * automatically add joins for all children (and theirs and theirs and theirs ...)
	 * @param domainClass The class for which to create the Query.
	 * @param deep Specify true if all "offspring" has to be added
	 * @deprecated Use DomainObjectQuery.getQuery, DomainObjectQuery.getChildQuery, or DomainObjectQuery.getGrandChildQuery instead
	 */
	@Deprecated
	public DomainObjectQuery(Class<T> domainClass, boolean deep)
	{
		this(domainClass, null, BusObjectMetadata.getMetadata(domainClass), null, new Query(), deep);
		
		query.setResultClass(null); // Result is AnonymousBusObject. ObjectMap class will create proper classes out of it.
    	query.setResultMapping(QueryObject.MAP_FLATTEN);
	}
	
	/**
	 * Create a new DomainObjectQuery for the domain class. If deep is true, then it will
	 * automatically add joins for all children (and theirs and theirs and theirs ...)
	 * @param domainClass The class for which to create the Query.
	 * @param deep Specify true if all "offspring" has to be added
	 */
	@SuppressWarnings("unchecked")
	public DomainObjectQuery(BusObjectMetadata metadata, boolean deep)
	{
		this((Class<T>)metadata.getDomainClass(), null, metadata, null, new Query(), deep);
		
		query.setResultClass(null); // Result is AnonymousBusObject. ObjectMap class will create proper classes out of it.
    	query.setResultMapping(QueryObject.MAP_FLATTEN);
	}
	
	private DomainObjectQuery(Class<T> domainClass, String structureAlias, BusObjectMetadata metadata, DomainObjectQuery<? extends BusObject> parentStructure, Query query, boolean deep)
	{
		this.domainClass = domainClass;
		this.metadata = metadata;
		if (this.metadata==null)
		{
			this.dbClass = null;
			this.dbRevisionClass = null;
			this.aliasName = structureAlias;
			this.parentDomainQuery = parentStructure;
			this.query = query;
			this.fromDBTable = null;
			return;
		}
		this.dbClass = metadata.getDbClass();
		this.dbRevisionClass = metadata.getDbRevisionClass();
		if (structureAlias==null)
		{
			// Take the table name as alias if none is given.
			structureAlias = metadata.getDbClassInfo().getTableName();
		}
		this.aliasName = structureAlias;
		this.parentDomainQuery = parentStructure;
		this.query = query;
		this.fromDBTable = query.getTable(dbClass, aliasName);
		
		// Select the attributes and add them to the query.
		selectAttributes(dbClass, fromDBTable);
		// Create the proper join with the parent (might go via mapping table)
		joinParent();
		// Join with the revision table (if available). This will also select it's fields
		//  Note that the order matters: first we have to join the parent, and then the revision table,
		//  otherwise the query will fail because of invalid syntax.
		joinRevisionTable();
		
		if (deep)
		{
			Collection<BusObjectMetadata> children = metadata.getDomainChildren();
			for (BusObjectMetadata busObjectMetadata : children)
			{
				addRelation(busObjectMetadata, deep);
			}
		}
	}
	
	/**
	 * Returns the DomainObjectQuery for the specified child class, or null if none was found.
	 * Null can be the case if the addRelation was not yet invoked, or if there is no relationship between
	 * parent and the child class.
	 * @param <O>
	 * @param childClass
	 * @return
	 */
	@Deprecated
	@SuppressWarnings("unchecked") // Implicitly done in code
	public <O extends BusObject> DomainObjectQuery<O> getRelation(Class<O> childClass)
	{
		// TODO: check whether this code is correct. compositeChildren does not have class as key; it should be metadata.
		
		// The cast is considered unsafe by Java. Nevertheless from code we know for sure
		//  that it is not possible to create a DOQ for something else than the child class
		return (DomainObjectQuery<O>) compositeChildren.get(childClass);
	}
	
	/**
	 * Adds a child to the query. Depending on the relationship, this will add one or more left outer joins to the Query.
	 * @param <O>
	 * @param childClass
	 * @return The DomainObjectQuery for the child object.
	 */
	@Deprecated
	public <O extends BusObject> DomainObjectQuery<O> addRelation(Class<O> childClass)
	{
		return addRelation(childClass, false);
	}
	
	/**
	 * Adds a child to the query. Depending on the relationship, this will add one or more left outer joins to the Query.
	 * @param <O>
	 * @param childClass
	 * @param deep Determines whether children of the relation also must be added. E.g., if for a Ticket we add a Project, then
	 * if we specify true, also the Users of the project will be added.
	 * @return The DomainObjectQuery for the child object.
	 */
	@Deprecated
	public <O extends BusObject> DomainObjectQuery<O> addRelation(Class<O> childClass, boolean deep)
	{
		if (!BusObjectMetadata.isDomainObject(childClass))
		{
			throw new BsfRuntimeException("Trying to add "+childClass.getSimpleName()+" to DomainObjectQuery "+this.domainClass.getSimpleName()+", but it is not a @DomainObject.");
		}
		BusObjectMetadata childMetadata = metadata.getDomainChild(childClass);
		return addRelation(childMetadata, deep);
	}
	
	BusObjectMetadata getMetadata()
	{
		return metadata;
	}
	
	/**
	 * Adds a relation to the query. Depending on the relationship, this will add one or more left outer joins to the Query.
	 * @param <O>
	 * @param childClass
	 * @return The DomainObjectQuery for the child object.
	 */
	public <O extends BusObject> DomainObjectQuery<O> addRelation(String relation)
	{
		return addRelation(relation, false);
	}
	
	/**
	 * Adds a child to the query. Depending on the relationship, this will add one or more left outer joins to the Query.
	 * @param <O>
	 * @param childClass
	 * @param deep Determines whether children of the relation also must be added. E.g., if for a Ticket we add a Project, then
	 * if we specify true, also the Users of the project will be added.
	 * @return The DomainObjectQuery for the child object.
	 */
	public <O extends BusObject> DomainObjectQuery<O> addRelation(String relation, boolean deep)
	{
		BusObjectMetadata childMetadata = metadata.getRelation(relation);
		
		// Check whether the relationship really exists.
		if (childMetadata==null)
		{
			throw new BsfRuntimeException("Trying to add the relation "+relation+" to DomainObjectQuery "+this.domainClass.getSimpleName()+", but it does not exist.");
		}
		return addRelation(childMetadata, deep);
	}
	
	public <O extends BusObject> DomainObjectQuery<O> addRelation(BusObjectMetadata childMetadata, boolean deep)
	{
    	@SuppressWarnings("unchecked") // Implicit casting for convenience reasons of the programming model.
    	// TODO: check whether this is the best approach!
		Class<O> childClass = (Class<O>)childMetadata.getDomainClass(); 
		RelationInfo_Composite relation = childMetadata.getParentRelationship();
		
		// Check whether the relationship really exists.
		if (relation==null)
		{
			throw new BsfRuntimeException("Trying to add "+childClass.getSimpleName()+" to DomainObjectQuery "+this.domainClass.getSimpleName()+", but it is not a child class.");
		}
		
		String childAliasName = aliasName+"_"+relation.getName();
		
		DomainObjectQuery<O> doq = new DomainObjectQuery<O>(childClass, childAliasName, childMetadata, this, query, deep);
		
		compositeChildren.put(childMetadata, doq);
		
		return doq;
	}
	
	
	
	/**
	 * Selects the fields from the specified table. Also registers each field,
	 * so that it can be related back to the domain object when the query result comes in.
	 */
	private void selectAttributes(Class<? extends BusObject> persistenceClass, Table from)
	{
		// Keep track of all the attributes in a separate collection.
		AttributeInfo[] attrs = BusObject._getClassInfo(persistenceClass).getAttributeInfos2();
		for (AttributeInfo attr : attrs)
		{
			String columnName = attr.getColumnName();
			
			
			AttributeInfo domainClassAttribute = BusObject._getClassInfo(domainClass).getAttributeInfo(columnName);
			if (domainClassAttribute==null)
			{
				// Well ... apparently we don't have this attribute in the domain class. Why bother selecting it?
				
				//TODO: we can also list the domain class attributes and compare in a case-insensitive manner. 
				// That way we could still align the attributes.
				// But then ... how would that work for mapping the update logic??
			}
			
			BusObjectAttribute attribute = new BusObjectAttribute(attr, from.getAliasName(), domainClassAttribute, false); 
			attributes.add(attribute);
			
			// For the alias we have to add the brackets into the query. In the result they do not come back.
			String columnAlias = attribute.getAliasName();
			from.select(columnName, columnAlias);
		}
		
	}
	
	void selectAttributeForParent(Table table, String columnName)
	{
		AttributeInfo dbAttribute = BusObject._getClassInfo(table.getPersistenceClass()).getAttributeInfo(columnName);
		BusObjectAttribute attribute = new BusObjectAttribute(dbAttribute, table.getAliasName(), null, true);
		attributes.add(attribute);
		// Now extend the query
		String columnAlias = attribute.getAliasName();
		table.select(columnName, columnAlias);
	}
	
	/**
	 * Check whether there is a Revision table related to the domain object, and, if so, join it into the query
	 *  and also select the fields from the revision table.
	 */
	private void joinRevisionTable()
	{
		if (dbRevisionClass==null)
		{
			return;
		}
		String revisionAlias = aliasName+"#"+metadata.getDbRevisionClassInfo().getTableName();
		fromRevisionTable = query.getTable(dbRevisionClass, revisionAlias);

		// TODO: leverage dynamism in field names from RevisionedObject
		RelationInfo_FK fk = BusObjectHelper.getForeignKeyRelation(dbClass, dbRevisionClass);
		
		// Here we need to use inner join to avoid reading deleted revisions (ExpiredOn is not null)
		fromDBTable.innerJoin(fromRevisionTable, fk).on(fromRevisionTable.getColumn(RevisionedObject.EXPIRED_ON), Operator.Equals, null);
		
		// And now also select the attributes from the Revision table
		selectAttributes(dbRevisionClass, fromRevisionTable);
	}
	
	/**
	 * Creates the join for this child with the parent. Also selects the children of the child.
	 * @param query
	 */
	private void joinParent()
	{
		if (parentDomainQuery==null)
		{
			// No parent, so also nothing to join with.
			return;
		}
		if (!BusObjectMetadata.isDomainObject(this.domainClass))
		{
			// If the child is not a domain object, we cannot query it in the database
			return;
		}
		
		RelationInfo_FK foreignKeyRelation = metadata.getParentForeignKey();
		if (foreignKeyRelation!=null)
		{
			Table joinParentTable = parentDomainQuery.fromDBTable;
			if (!parentDomainQuery.dbClass.equals(BusObjectHelper.getRelatedClass(foreignKeyRelation)))
			{
				joinParentTable = parentDomainQuery.fromRevisionTable;
			}
			// Now extend the query with the proper join with the parent
			joinParentTable.leftOuterJoin(fromDBTable, foreignKeyRelation);
		}
		else
		{
			
			// No need to check whether there is a relationship with the Revision table, all children always link to the parent
			//  table directly (this has been the case until now. Probably in future applications of Revisioning pattern this may be required).
			if (!joinMappingTable())
			{
				// Did not find a relationship between child and parent.
				DomainObjectQuery<?> current = parentDomainQuery;
				while (current.parentDomainQuery!=null)
				{
					current = current.parentDomainQuery;
				}
				String rootObject = current.domainClass.getSimpleName();
				throw new BsfRuntimeException("Error while creating DomainObjectQuery for "+rootObject+".\nCannot find a database relationship between "+dbClass.getSimpleName()+" and "+parentDomainQuery.dbClass.getSimpleName());
			}
		}
	}
	
	/**
	 * If a relation with the parent is not found via a direct foreign key, then it may be available
	 * through a mapping table.
	 */
	private boolean joinMappingTable()
	{
		Class<? extends BusObject> mappingClass = metadata.getMappingClass();
		if (mappingClass==null)
		{
			// A mapping table between the parent and child structures was not found. Probably there is no relationship, 
			//  and this may be a mis-configuration in the domain model. Should we raise an error? Perhaps we can do this
			//  once the BusObjectStructure class is created at start time.
			return false;
		}
		
		// And the metadata about the mapping table
		String mappingTableName = BusObject._getClassInfo(mappingClass).getTableName();
		String mappingAlias = aliasName+"_"+mappingTableName;
		fromMappingTable = query.getTable(mappingClass, mappingAlias);
		
		// Extend the query with a join from the parent to the mapping table. // TODO: ouch. This might just as well be the revision table!
		parentDomainQuery.fromDBTable.leftOuterJoin(fromMappingTable, BusObjectHelper.getForeignKeyRelation(parentDomainQuery.dbClass, mappingClass));
		// Now extend the query with the join on the child table.
		fromMappingTable.leftOuterJoin(fromDBTable, BusObjectHelper.getForeignKeyRelation(mappingClass, dbClass));
		// And also select the attributes from the mapping table
		selectAttributes(mappingClass, fromMappingTable);
		return true;
	}
	
	/**
	 * Returns the Table from which the plain database fields will be read for this DomainObject.
	 * This is an aliased table. It can be extended with extra columns, or it can be used to create an additional where clause.
	 * <code>
	 *    DomainObjectQuery<Project> projectQuery = new DomainObjectQuery(Project.class, false);
	 *    DomainObjectQuery<User> userQuery = projectQuery.addRelation(User.class);
	 *    Table userTable = userQuery.getFromTable();
	 *    Query sqlQuery = projectQuery.getQuery();
	 *    sqlQuery.where(userTable.getColumn("Email"), Operator.Equals, "jsmith@gmail.com");
	 * </code>
	 * @return The aliased db table.
	 * @return
	 */
	public Table getFromTable()
	{
		return fromDBTable;
	}
	
	/**
	 * Returns the Table from which the revision fields will be read for this DomainObject
	 * This is an aliased table. It can be extended with extra columns, or it can be used to create an additional where clause.
	 * @return The aliased revision table.
	 */
	public Table getFromRevisionTable()
	{
		return fromRevisionTable;
	}
	
	/**
	 * Returns the "native" query, which can be extended with e.g. where clauses.
	 * @return
	 */
	public Query getQuery()
	{
		return query;
	}
	
	/**
	 * Executes the query and returns the first result.
	 * @return
	 */
	public T getObject()
	{
    	BusObjectIterator<BusObject> flatRecords = query.getObjects();
    	ObjectMap<T> map = new ObjectMap<T>();
    	
    	while (flatRecords.hasMoreElements())
    	{
    		BusObject record = flatRecords.nextElement();
    		map.mergeRecord(record, this);
    	}
		
		return map.getObject();
	}
	
	/**
	 * Executes the query and returns the objects found.
	 * @return
	 */
	public BusObjectIterator<T> getObjects()
	{
		// Execute the query
    	BusObjectIterator<BusObject> flatRecords = query.getObjects();
    	// Create a map for the DomainObjects to map the query result back into
    	ObjectMap<T> map = new ObjectMap<T>();
    	// Iterate the query result and merge it into the map
    	while (flatRecords.hasMoreElements())
    	{
    		map.mergeRecord(flatRecords.nextElement(), this);
    	}
		// Return the domain objects.
		return map.getObjects();
	}
	
	private BusObject grandParent;
	private Map<BusObjectUID, BusObject> children = new HashMap<BusObjectUID, BusObject>();
	private RelationInfo_Composite parentRelation;
	private RelationInfo_Composite grandChildRelation;
	
	public void loadObjectsInGrandParent(BusObject grandParent, RelationInfo_Composite parentRelation, RelationInfo_Composite grandChildRelation)
	{
		this.grandParent = grandParent;
		this.parentRelation = parentRelation;
		this.grandChildRelation = grandChildRelation;
		
		if (parentRelation.isMultiOcc())
		{
			// Now load the child objects, and merge them with the resulting objects.
			MultiRelation instanceRelation = this.grandParent._getMultiRelation(parentRelation.getIdentifier(), true);
			BusObjectIterator<?> currentChildren = instanceRelation.getObjects();
			while (currentChildren.hasMoreElements())
			{
				BusObject object = currentChildren.nextElement();
				children.put(BusObjectHelper.getUID(object), object);
			}
		}
		else
		{
			throw new BsfRuntimeException("This method only works for multi-occ relations");
		}
		
		this.getObjects();
	}
	
	/**
	 * Returns the domain class for this query.
	 * @return
	 */
	Class<T> getDomainClass()
	{
		return domainClass;
	}
	
	public String toString()
	{
		return query.toString();
	}
	
	/**
	 * This class is responsible for converting a series of records
	 * into a (possibly nested) BusObject structure.
	 * 
	 * E.g., all fields from tables Ticket, Component, Project, Note and User are converted into a nested
	 * xml structure (omitted xml tags)
	 * <pre>
	 * Ticket
	 *    Id - 123
	 *    Description - Ticket Description
	 *    Component
	 *    	Id - 1
	 *    	Description - My main component
	 *    Project
	 *    	Id - 3
	 *    	Description - My current project
	 *    Note
	 *    	Description - Please do something with this ticket
	 *    	User
	 *    		Name - customer
	 *    		Id - 3
	 *    Note
	 *    	Description - I thought it is already fixed. I will look into it again.
	 *    	User
	 *    		Name - engineer
	 *    		Id - 14
	 * </pre>
	 * @param <O> In the above example this will be the Ticket class.
	 */
	class ObjectMap<O extends BusObject>
	{
		/**
		 * This map contains the root objects of the result by their key.
		 * E.g., when retrieving a list of Tickets with children, this contains only the Ticket objects,
		 *  hashed by their key. This is required for easy lookup.
		 */
		private Map<ObjectKey, ObjectValue> objectsByKey = new HashMap<ObjectKey, ObjectValue>();
		/**
		 * This is an ordered list of the root objects, in the order that they are received in the
		 * query result.
		 */
		private List<ObjectValue> objects = new ArrayList<ObjectValue>();
		
		/**
		 * Create a new ObjectMap for the corresponding domain query.
		 * @see DomainObjectQuery
		 * @param structure The structure describing how the query response can
		 * be mapped back into the BusObject structure. 
		 */
		private ObjectMap()
		{
		}
		
		/**
		 * Returns a single result of the response.
		 * @return
		 */
		private O getObject()
		{
			BusObjectIterator<O> objects = getObjects();
			return objects.hasMoreElements() ? objects.nextElement() : null;
		}
		
		/**
		 * Returns all the response objects.
		 * @return
		 */
		@SuppressWarnings("unchecked")
		private BusObjectIterator<O> getObjects()
		{
			Collection<O> busObjects = new ArrayList<O>();
			for (ObjectValue value : objects)
			{
				// First sort the children properly.
				value.addChildren();
				// Now add them to the response.
				busObjects.add((O) value.busObject);
			}
			BusObjectArray<O> result = new BusObjectArray<O>(busObjects);
			return result;
		}
		
		/**
		 * Adds one query result to the object map.
		 * @param record A query result, typically contains many fields from many tables
		 */
		private void mergeRecord(BusObject record, DomainObjectQuery<O> domainQuery)
		{
			// Invokes a recursive function to compose a bus object
			//  starting from the top structure (describing e.g. Ticket)
			composeBusObject(domainQuery, record, null);
		}

		/**
		 * Recursive function that creates a new BusObject, by taking out those values
		 * of the record that correspond to the given structure.
		 * Additionally adds the new BusObject to the parent, if available.
		 * @param domainQuery The structure holding the definition that is used to compose the BusObject.
		 * @param record The record holding the data.
		 * @param parent The parent bus object. If null is passed, then this the new BusObject is a root object.
		 */
		private void composeBusObject(DomainObjectQuery<?> domainQuery, BusObject record, ObjectValue parent)
		{
			BusObject newObject = null;
			BusObject transientParent = null;
			boolean hasOnlyNullValues = true;
			// Now copy the attributes into the new object.
			for (BusObjectAttribute classAttribute : domainQuery.attributes)
			{
				String attributeNameInRecord = classAttribute.getAliasName();
				if (classAttribute.isParentKey())
				{
					if (transientParent==null)
					{
						try
						{
							Class<? extends BusObject> parentClass = BusObjectHelper.getRelatedClass(domainQuery.parentRelation);
							transientParent = parentClass.newInstance();
							transientParent.makeTransient();
						}
						catch (InstantiationException e)
						{
							throw new RuntimeException(e); // will not happen
						}
						catch (IllegalAccessException e)
						{
							throw new RuntimeException(e); // will not happen
						}
					}
					String value = record._getElement(attributeNameInRecord);
					// We always set this property by it's database name, and never by domain object name (since that is not available).
					transientParent.setProperty(classAttribute.getDBAttributeInfo().getName(), value, 0);
					//  Do not do the remainder of the logic (don't load this attribute on the newObject, also don't put it in the object map)
					continue;
				}
				
				if (newObject==null)
				{
					try
					{
						newObject = (BusObject) domainQuery.domainClass.newInstance();
						newObject.makeTransient();
					}
					catch (InstantiationException e)
					{
						throw new RuntimeException(e); // will not happen
					}
					catch (IllegalAccessException e)
					{
						throw new RuntimeException(e); // will not happen
					}
				}
				String value = record._getElement(attributeNameInRecord);
				AttributeInfo domainAttribute = classAttribute.getDomainAttributeInfo();

				
				// By default take the name of the db attribute ...
				//  In principle these do NOT belong in the domain object!!
				// ... but ... unfortunately (or not) ...
				//  In case of "half" objects, it can be very very useful to still have the
				// information of the database available. Typically these are foreign key fields
				// such as Note.TicketId; a field like that is required if a DomainObjectQuery
				//  result has to be used to invoke QueryUtils.loadRelation.
				String attributeNameInObject = classAttribute.getDBAttributeInfo().getName();
				if (domainAttribute!=null) // ... but of course always take the domain attribute name if it is available!
				{
					attributeNameInObject = domainAttribute.getName();
				}
				
				if (!record.isNull(attributeNameInRecord))
				{
					hasOnlyNullValues = false;
					newObject.setProperty(attributeNameInObject, value, 0);
				}
			}
			
			if (newObject==null || hasOnlyNullValues)
			{
				// Apparently no attributes have been found in the record, so returning.
				return;
			}
			
			// Put it in the result map.
			ObjectKey key = new ObjectKey(newObject, domainQuery);
			ObjectValue value = null;
			
			// If there is no parent, then this is a root object.
			//  If not, then we need to add the new object as a child to the parent.
			if (parent==null)
			{
				value = objectsByKey.get(key);
				if (value==null)
				{
					value = new ObjectValue(newObject);
					objectsByKey.put(key, value);
					objects.add(value);
					
					if (transientParent!=null)
					{
						BusObjectUID parentKey = BusObjectHelper.getUID(transientParent);
						BusObject realParent = children.get(parentKey);
						if (realParent!=null)
						{
							if (grandChildRelation.isMultiOcc())
							{
								// Now load the child objects, and merge them with the resulting objects.
								MultiRelation instanceRelation = realParent._getMultiRelation(grandChildRelation.getIdentifier(), true);
								instanceRelation.addObject(newObject);
							}
							else
							{
								// Note: there could already be an object loaded here. If so, then it is overwritten....
								SingleRelation instanceRelation = realParent._getSingleRelation(grandChildRelation.getIdentifier(), true);
								instanceRelation.setLocalObject(newObject);
							}		
						}
						else
						{
							// real strange!
						}
					}
				}
			}
			else
			{
				value = parent.addChild(key, newObject, domainQuery);
			}
			
			// Now iterate the children.
			for (DomainObjectQuery<?> child : domainQuery.compositeChildren.values())
			{
				composeBusObject(child, record, value);
			}
		}
	}
	
	/**
	 * Wrapper for a BusObject and it's children.
	 */
	private class ObjectValue
	{
		/**
		 * The actual BusObject for this value.
		 */
		private final BusObject busObject;
		/**
		 * Contains all the child objects, for easy lookup.
		 */
		private final Map<ObjectKey, ObjectValue> objects = new HashMap<ObjectKey, ObjectValue>();
		/**
		 * A list of child objects, ordered by their relation with the object.
		 */
		private final Map<RelationInfo_Composite, List<ObjectValue>> childrenPerRelation = new HashMap<RelationInfo_Composite, List<ObjectValue>>();
		/**
		 * Flag that indicates whether all children are added into the parent.
		 */
		private boolean childrenHaveBeenAddedToBusObject = false;
		/**
		 * Create a new Object Value wrapping the BusObject.
		 * @param object
		 */
		private ObjectValue(BusObject object)
		{
			this.busObject = object;
		}
		
		/**
		 * Adds a child for the value, without adding it to the BusObject itself.
		 * This is done to be able to have a nice response structure. Each record is 
		 * passed individually to the ObjectMap, and from the record all relevant children
		 * are taken and added to the BusObject. However, subsequent records may contain
		 * additional children of the same type. If we would immediately add the child
		 * to the BusObject, then the end result will contain the children in a somewhat
		 * arbitrary and less readable format. Hence sorting the children only after
		 * all records have been offered to the ObjectMap.
		 * @param child
		 * @param childDomainQuery
		 */
		private ObjectValue addChild(ObjectKey key, BusObject child, DomainObjectQuery<?> childDomainQuery)
		{
			ObjectValue value = this.objects.get(key);
			if (value!=null) // It is already present, skip this one.
			{
				return value;
			}

			// Create a new wrapper for this child
			value = new ObjectValue(child);
			// Store it in the directly accessible cache of children ...
			this.objects.put(key, value);
			// ... and also store it with the corresponding relationship, in order to be able to nicely sort it out later.
			RelationInfo_Composite relation = childDomainQuery.metadata.getParentRelationship();
			List<ObjectValue> currentChildrenForThisRelation = childrenPerRelation.get(relation);
			if (currentChildrenForThisRelation==null)
			{
				currentChildrenForThisRelation = new ArrayList<ObjectValue>();
				childrenPerRelation.put(relation, currentChildrenForThisRelation);
			}
			currentChildrenForThisRelation.add(value);
			// and give it back, so that it can be used for further references.
			return value;
		}
		
		/**
		 * Add the children to the parent. This is done only once.
		 */
		private void addChildren()
		{
			if (childrenHaveBeenAddedToBusObject)
			{
				return;
			}
			// Iterate the relations, and for each relation,
			//  iterate the children and add each child to the parent object.
			Set<RelationInfo_Composite> relations = childrenPerRelation.keySet();
			for (RelationInfo_Composite relation : relations)
			{
				if (relation.isMultiOcc())
				{
					MultiRelation childrenRelation = busObject._getMultiRelation(relation.getIdentifier(), true);
					childrenRelation.load();
					List<ObjectValue> children = childrenPerRelation.get(relation);
					for (ObjectValue child : children)
					{
						childrenRelation.addObject(child.busObject);
						child.addChildren();
					}
				}
				else
				{
					SingleRelation childRelation = busObject._getSingleRelation(relation.getIdentifier(), true);
					childRelation.load();
					// Should in practice never be more than one. If still more than one is found
					//  only the last one will be actually set in the result.
					List<ObjectValue> children = childrenPerRelation.get(relation);
					for (ObjectValue child : children)
					{
						childRelation.setLocalObject(child.busObject);
						child.addChildren();
					}
				}
			}
			
			// Now invoke onAfterLoad handler on the BusObject.
			//  Notice: this is a direct invocation, so we do not do the "real" onAfterLoad event
			//  Any exceptions in the onAfterLoad handler will be re-thrown.
			try
			{
				Method onAfterLoadMethod = busObject.getClass().getMethod("onAfterLoad");
				onAfterLoadMethod.invoke(busObject);
			}
			catch (SecurityException e) {} // Oops, sorry.
			catch (NoSuchMethodException e) {} // Ok, no issue.
			catch (IllegalArgumentException e) {} // Hmmm, must have been another one?
			catch (IllegalAccessException e) {} // Oops, sorry.
			catch (InvocationTargetException e)
			{
				// Ok, this one cannot be ignored.
				throw new BsfApplicationRuntimeException("Error while invoking custom onAfterLoad handler", e);
			}
			childrenHaveBeenAddedToBusObject = true;
		}
	}
	/**
	 * Wrapper to determine the key value of the BusObject.
	 * Assumes the BusObject has UID elements.
	 */
	private class ObjectKey
	{
		private final BusObject object;
		private final String key; // Custom string representation of the object's key
		
		ObjectKey(BusObject object, DomainObjectQuery<?> childDomainQuery)
		{
			this.object = object;
			
			// Now determine the key of the object.
			//  In practice it will be something like "crom.example.Ticket-Id=1, "
			String[] uidAttributes = BusObject._getClassInfo(this.object.getClass()).getUIDElements();
			StringBuffer keyBuffer = new StringBuffer(this.object.getClass().getCanonicalName()+"-");
			for (String string : uidAttributes)
			{
				keyBuffer.append(string+"="+this.object.getStringProperty(string)+", ");
			}
			RelationInfo_Composite myRelationWithParent = childDomainQuery.metadata.getParentRelationship();
			String relationName = myRelationWithParent!=null ? myRelationWithParent.getIdentifier() : "";
			keyBuffer.append(" as " + relationName);
			this.key = keyBuffer.toString();
		}
		public String toString()
		{
			return key;
		}
		public int hashCode()
		{
			return key.hashCode();
		}
		public boolean equals(Object that)
		{
			if (this==that) return true;
			if (that==null) return false;
			return (this.toString().equals(that.toString()));
		}
	}
}
